Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

283
Views
Java generics: Does "T2 extends T1" imply "T1 super T2"?

If I have a generic <T2 extends T1>, does the compiler infer T1 super T2?

I have a more complex collection, which I reduced to the MWE below. The collection shall be mergeable with any such collection with elements of any subtype.

Now I'm wondering, why the call to forEach in merge is not accepted. It fails with

java.util.function.Consumer<java.util.Optional> cannot be converted to java.util.function.Consumer<java.util.Optional<? super T2>>

I've depicted the type relationships in the diagram below. In merge, T2 extends T1. The call to forEach is applied on the other object, hence the T1 of this becomes the ? of other.forEach and the T2 of this.merge is the T1 of other. Hence, this' T1 should be accepted as super of other's T1.

I also tried public void merge(Group<? extends T> other) with the same result. And public void merge(Group<T> other) does not accept such collections with elements of any subtype of T1.

MWE:

class Group<T1> {
    public <T2 extends T1> void merge(Group<T2> other) {
        Consumer<Optional<T1>> task = t -> t.ifPresentOrElse(this::foo, this::bar);
        other.forEach(task);
    }

    public Collection<Optional<T1>> values() {
        return List.of();
    }

    public void forEach(Consumer<Optional<? super T1>> consumer) {
        values().forEach(consumer);
    }

    private void foo(T1 t) {}

    private void bar() {}
}

Relationships:

   this.T1      -becomes->  other.forEach.?
      ^                            |
      |                          super
   extends                         |
      |                            v
this.merge.T2     -is->        other.T1
over 4 years ago · Santiago Trujillo
2 answers
Answer question

0

If you have a Consumer<Optional<? super T1>> consumer then you can pass it (to the accept(T) method) an Optional<Object> because Object is a supertype of T1.

On the other hand, you cannot pass an Optional<Object> to Consumer<Optional<T1>> task because T1 might not be Object.

So you cannot assign the Consumer<Optional<T1>> task to the Consumer<Optional<? super T1>> consumer to prevent your consumer from being passed objects of types it does not expect.

over 4 years ago · Santiago Trujillo Report

0

You can solve your problem using

class Group<T1> {
    public <T2 extends T1> void merge(Group<T2> other) {
        Consumer<Optional<? extends T1>> task=t->t.ifPresentOrElse(this::foo, this::bar);
        other.forEach(task);
    }

    public Collection<Optional<T1>> values() {
        return List.of();
    }

    public void forEach(Consumer<? super Optional<T1>> consumer) {
        values().forEach(consumer);
    }

    private void foo(T1 t) {}

    private void bar() {}
}

This is an application of the PECS rule.

Your task will treat the Optional as a producer, hence, has to declare Optional<? extends T1> to allow the optional to produce subtypes of T1. We know that optional will always act as a producer, but Java’s generic type system does not have the concept of classes always acting like a producer or consumer and needs an explicit ? extends T1 here.

The forEach receives a consumer, even literally in the interface name, so ? super … is the right way to declare that this consumer may be a consumer of supertypes of Optional<T1>. This does not only include Object, the superclass of Optional, but also Optional<? extends T1> when T1 := T2, as in the invocation within merge, as Optional<? extends T1> is a supertype of Optional<T2>.


Of course, you could also solve the issue by simply using

Consumer<Optional<T2>> task = t -> t.ifPresentOrElse(this::foo, this::bar);

as this only requires foo to accept a T2 argument, which it always does, as T2 is a subtype of T1. But changing the signature of forEach to raise the flexibility is always a good move. Since it’s a public method, there might be other callers benefitting from it.

over 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!